package bilivod

import (
	"encoding/json"
	"fmt"
	"io"
	"iptv_server/v3/v2_src/utils"
	"net/http"
	"net/http/cookiejar"
	"path"
	"regexp"
	"strings"
	"time"
)

const (
	FROM_BILIBILI  string = "from_bili"
	_SEASON_PREFIX string = "season_id_"

	// qn_720p = 64
	// qn_480p = 32
	// qn_360p = 16
)

var (
	agent         *bili
	result_header string

	aidPattern *regexp.Regexp
)

func initResultHeader() {
	header := make(map[string]string)
	header["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"
	header["Referer"] = "https://www.bilibili.com"
	// header["__ffff__"] = "p2"

	if bs, err := json.Marshal(header); err == nil {
		result_header = string(bs)
	}

	//// 特殊 自定义参数,p2=playerType=exo
	//// # 0=系统,1=ijk,2=exo
	// header_result={
	//     #"__ffff__":"p2"
	//     "__ffff__":"p2;;ooo"
	// }
}

func tick() {
	ticker := time.NewTicker(time.Minute * 10)
	count := 0
	for {
		t := <-ticker.C
		agent = nil
		if count%12 == 0 {
			utils.Logger.Debugf("tick %v", t)
		}
		count++
	}
}

func init() {
	initResultHeader()
	aidPattern = regexp.MustCompile(`"aid":(\d+),`)

	go tick()
}

type bili struct {
	client *http.Client
	apiurl string
}

func (b *bili) get_aid_by_bvid(bvid string) string {
	url := fmt.Sprintf("https://www.bilibili.com/video/%s/", bvid)
	bs := b.getContent(url)
	if len(bs) == 0 {
		return ""
	}

	bss := aidPattern.FindSubmatch(bs)
	if len(bss) < 2 {
		return ""
	}
	return string(bss[1])
}

func (b *bili) get_aid(bvid string) string {
	a := strings.ToLower(bvid)
	if strings.HasPrefix(a, "bv") {
		return b.get_aid_by_bvid(bvid)
	} else if strings.HasPrefix(a, "av") {
		return bvid[2:]
	}
	return ""
}

func (b *bili) get(url string, referer bool) *http.Response {
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil
	}
	req.Header.Add("User-Agent", utils.UA)
	if referer {
		req.Header.Add("Referer", "https://www.bilibili.com")
	}
	resp, err := b.client.Do(req)
	if err != nil {
		fmt.Printf("[!fetch] err: %v\n", err)
		return nil
	}
	return resp
}

func (b *bili) getContent(url string) (bs []byte) {
	resp := b.get(url, true)
	if resp == nil {
		return
	}

	var err error
	bs, err = io.ReadAll(resp.Body)
	resp.Body.Close()

	if err != nil {
		return []byte("")
	}
	return bs
}

type detail struct {
	Data struct {
		Pages []struct {
			Cid  int    `json:"cid"`
			Part string `json:"part"`
		} `json:"pages"`
	} `json:"data"`
}

type season struct {
	Title  string `json:"title"`
	SeasonId  int `json:"season_id"`
	Result struct {
		Episodes []struct {
			Id        int    `json:"id"`
			Cid       int    `json:"cid"`
			Title     string `json:"title"`
			LongTitle string `json:"long_title"`
		} `json:"episodes"`
		Stat struct {
			Views int64 `json:"views"` //播放量
		} `json:"stat"`
	} `json:"result"`
}

func parseAndGenPlaylist(bs []byte, aid string, apiurl string) string {
	utils.Logger.Debugf("parseAndGenPlaylist %s", string(bs))

	obj := &detail{}
	if json.Unmarshal(bs, obj) != nil {
		return ""
	}

	if len(obj.Data.Pages) == 0 {
		return ""
	}
	if len(obj.Data.Pages) == 1 {
		cid := obj.Data.Pages[0].Cid
		if cid == 0 {
			return ""
		}
		title := "1"
		utils.Logger.Debugf("1(only), cid=%d,part=%s", cid, title)
		return fmt.Sprintf("%s$%s",
			title,
			path.Join(apiurl, FROM_BILIBILI, aid, fmt.Sprintf("%d", cid)),
		)
	}
	ss := make([]string, 0, 15)
	i := 0
	for _, page := range obj.Data.Pages {
		cid := page.Cid
		if cid == 0 {
			continue
		}
		i++
		title := strings.ReplaceAll(page.Part, "#", "-")
		s := fmt.Sprintf("%d %s$%s",
			i, title,
			path.Join(apiurl, FROM_BILIBILI, aid, fmt.Sprintf("%d", cid)),
		)
		utils.Logger.Debugf("%02d,cid=%d,part=%s", i, cid, title)
		ss = append(ss, s)
	}
	return strings.Join(ss, "#")
}

func parseAndGenPlaylist_season(bs []byte, sid string, apiurl string) string {
	_ = sid

	utils.Logger.Debugf("parseAndGenPlaylist_season %s", string(bs))

	obj := &season{}
	if json.Unmarshal(bs, obj) != nil {
		return ""
	}
	if len(obj.Result.Episodes) == 0 {
		return ""
	}

	ss := make([]string, 0, 10)
	i := 0
	for _, v := range obj.Result.Episodes {
		eid := v.Id
		cid := v.Cid
		if cid == 0 {
			continue
		}
		i++
		var title string
		if len(v.LongTitle) > 0 {
			title = v.LongTitle
		} else if len(v.Title) > 0 {
			title = v.Title
		} else {
			title = fmt.Sprintf("%d", i)
		}
		title = strings.ReplaceAll(title, "#", "-") // 貌似是 序号
		// s := fmt.Sprintf("%s$%s/%s%s/%d_%d",
		s := fmt.Sprintf("%s$%s",
			title,
			path.Join(apiurl, FROM_BILIBILI, _SEASON_PREFIX+sid, fmt.Sprintf("%d_%d", eid, cid)),
		)
		utils.Logger.Debugf("%02d,eid=%d,cid=%d,part=%s", i, eid, cid, title)
		ss = append(ss, s)
	}
	return strings.Join(ss, "#")
}

func (b *bili) getPlaylist_season(sid string) string {
	// sid==seasonID
	url := "http://api.bilibili.com/pgc/view/web/season?season_id=" + sid

	bs := b.getContent(url)
	if len(bs) == 0 {
		return ""
	}

	return parseAndGenPlaylist_season(bs, sid, b.apiurl)
}

func (b *bili) getPlaylist(aid string) string {

	if aid2, ok := strings.CutPrefix(aid, _SEASON_PREFIX); ok {
		return b.getPlaylist_season(aid2)
	}

	// detailUrl
	url := "https://api.bilibili.com/x/web-interface/view?aid=" + aid
	bs := b.getContent(url)
	if len(bs) == 0 {
		return ""
	}

	return parseAndGenPlaylist(bs, aid, b.apiurl)
}

type durl struct {
	Url  string `json:"url"`
	Size int    `json:"size"` //清晰度, max(size)=>最高清晰度
}

type video struct {
	Data struct {
		Durl []durl `json:"durl"`
	} `json:"data"`
}
type videoSeason struct {
	Message string `json:"message"`
	Result  struct {
		Durl []durl `json:"durl"`
	} `json:"result"`
}

type result struct {
	Parse       int    `json:"parse"`
	PlayUrl     string `json:"playUrl"`
	Url         string `json:"url"`
	Header      string `json:"header"`
	ContentType string `json:"contentType"`
	Player      int    `json:"player"`
}

func (r *result) Set(url string) {
	r.Url = url

	r.Parse = 0
	r.PlayUrl = ""
	r.ContentType = "video/x-flv"
	r.Header = result_header

	r.Player = 2
	// playerType:
	// 		sys == 0
	// 		ijk == 1;
	// 		exo == 2;
}

func (r *result) Marshal() string {
	if bs, err := json.Marshal(r); err == nil {
		return string(bs)
	}
	return ""
}

func getHighestResolution(ds []durl) (url string) {
	var dMax durl = ds[0]
	for _, d := range ds {
		if d.Size > dMax.Size {
			dMax = d
		}
	}
	url = dMax.Url
	if utils.IsHttpUrl(url) {
		return url
	}
	url = ds[0].Url //fallback
	if utils.IsHttpUrl(url) {
		return url
	}
	return ""
}

func parseAndGen_season(bs []byte) string {
	utils.Logger.Debugf("parseAndGen_season %s", string(bs))

	obj := &videoSeason{}
	if json.Unmarshal(bs, obj) != nil {
		return ""
	}
	if len(obj.Result.Durl) == 0 {
		return ""
	}
	if obj.Message != "success" {
		utils.Logger.Warnf("需要大会员权限才能观看")
		return ""
	}
	url := getHighestResolution(obj.Result.Durl)
	if len(url) == 0 {
		return ""
	}
	ret := &result{}
	ret.Set(url)
	return ret.Marshal()

}

func parseAndGen(bs []byte) string {
	utils.Logger.Debugf("parseAndGen %s", string(bs))
	obj := &video{}
	if json.Unmarshal(bs, obj) != nil {
		return ""
	}
	if len(obj.Data.Durl) == 0 {
		return ""
	}
	url := getHighestResolution(obj.Data.Durl)
	if len(url) == 0 {
		return ""
	}
	ret := &result{}
	ret.Set(url)
	return ret.Marshal()

}

func (b *bili) getVideoUrl_season(aid, cid string) string {
	_ = aid

	ids := strings.Split(cid, "_")
	// epid, cid
	if len(ids) < 2 {
		return ""
	}

	// qn_720p = 64
	// qn_480p = 32
	// qn_360p = 16

	// #qn 116
	// # url = 'https://api.bilibili.com/pgc/player/web/playurl?ep_id={0}&cid={1}&qn={2}'.format(ids[0],ids[1],qn_480p)
	// url := fmt.Sprintf("https://api.bilibili.com/pgc/player/web/playurl?ep_id=%s&cid=%s&qn=%d", ids[0], ids[1], 0)
	// # &fnver=0&fnval=4048
	// #  uuu='https://api.bilibili.com/pgc/player/web/v2/playurl?qn=0&from_client=BROWSER&ep_id={}&cid={}'.format(ids[0],ids[1])
	// #https://api.bilibili.com/pgc/player/web/v2/playurl?support_multi_audio=true&avid=209227199&cid=4837199&qn=0&fnver=0&fnval=4048&fourk=1&gaia_source=&from_client=BROWSER&ep_id=148741&session=229d4d2dcfccfb38a0a76e97208e9bdf&drm_tech_type=2

	// "accept_quality":[64,16]

	// 下面两个没区别...
	// url := fmt.Sprintf("https://api.bilibili.com/pgc/player/web/playurl?ep_id=%s&cid=%s&qn=%d&fnver=0&fnval=4048", ids[0], ids[1], 0) // 不行了

	// ==OK
	url := fmt.Sprintf("https://api.bilibili.com/pgc/player/web/playurl?ep_id=%s&cid=%s&qn=%d", ids[0], ids[1], 16) //qn_360p

	bs := b.getContent(url)
	if len(bs) == 0 {
		return ""
	}

	return parseAndGen_season(bs)
}

func (b *bili) getVideoUrl(aid, cid string) string {
	if aid2, ok := strings.CutPrefix(aid, _SEASON_PREFIX); ok {
		return b.getVideoUrl_season(aid2, cid)
	}
	// play
	//qn 112
	// v_url = "https://api.bilibili.com/x/player/playurl?avid={0}&cid=%20%20{1}&qn={2}".format(aid,cid,qn_720p)
	// v_url = "https://api.bilibili.com/x/player/playurl?avid={0}&cid=%20%20{1}&qn=%d".format(aid, cid, 112)

	url := fmt.Sprintf("https://api.bilibili.com/x/player/playurl?avid=%s&cid=%s&qn=%d", aid, cid, 112)

	bs := b.getContent(url)
	if len(bs) == 0 {
		return ""
	}

	return parseAndGen(bs)
}

func (b *bili) init() bool {
	jar, err := cookiejar.New(nil)
	if err != nil {
		return false
	}
	b.client = &http.Client{
		Jar:     jar,
		Timeout: time.Second * 10,
	}

	utils.Logger.Debugf("bili vod : init %s", "")

	resp := b.get("https://www.bilibili.com/", false)
	if resp != nil {
		resp.Body.Close()
	}
	return true
}

const (
	_GET_BILI_AID string = "get_bili_aid"
)

func GetData(aid, cid string, apiurl string) string {
	a := agent
	if a == nil {
		a = &bili{
			apiurl: apiurl,
		}
		if a.init() {
			agent = a
		} else {
			return ""
		}
	}

	// a := &bili{}
	// if a.init() {
	// 	agent = a
	// } else {
	// 	return ""
	// }

	if len(cid) == 0 {
		return a.getPlaylist(aid)
	} else if strings.Contains(cid, _GET_BILI_AID) {
		return a.get_aid(aid)
	} else {
		return a.getVideoUrl(aid, cid)
	}
}
